/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "communication/spatial_communicator.h"


#include "communication/message_builder.h"
#include "hilog_wrapper.h"
#include "spatial_awareness_common.h"

namespace OHOS {
namespace Msdp {
SpatialCommunicator::SpatialCommunicator()
{
}

SpatialCommunicator::~SpatialCommunicator()
{
}

void SpatialCommunicator::Init(std::shared_ptr<ICommunicateInterface> adapter)
{
    adapter_ = adapter;
    adapter_->RegisterListoner(shared_from_this());
    adapter_->PublishService();
}

DeviceInfo SpatialCommunicator::GetLocalDeviceInfo()
{
    // To be added
    return adapter_->GetLocalDeviceInfo();
}

int32_t SpatialCommunicator::StartDiscovery(DiscoverCallback callback)
{
    discovering_ = true;
    discoverCb_ = callback;
    return adapter_->StartDiscovery();
}

int32_t SpatialCommunicator::GetTrustDeviceList(GetTrustListCallback callback)
{
    getting_ = true;
    trustDeviceCb_ = callback;
    return adapter_->GetTrustDeviceList();
}

int32_t SpatialCommunicator::RegisterDeviceStateListener(DeviceStateCallback callback)
{
    stateCb_ = callback;
    return adapter_->RegisterDevStateCallback();
}

int32_t SpatialCommunicator::UnregisterDeviceStateListener()
{
    stateCb_ = nullptr;
    return SPATIALMANAGER_OK;
}

int32_t SpatialCommunicator::StopDiscovery()
{
    if (!discovering_) {
        return RET_SUCCESS;
    }
    return adapter_->StopDiscovery();
}

bool SpatialCommunicator::IsDiscoveryComplete()
{
    return !discovering_;
}

bool SpatialCommunicator::IsGetDeviceComplete()
{
    return !getting_;
}

std::shared_ptr<SpatialCommunicator::Session> SpatialCommunicator::GetCommunicatorSession(int sessionId)
{
    SPATIAL_LOGI(MODULE_SERVICE, "sessionMap_.size: %{public}zu", sessionMap_.size());
    if (sessionMap_.empty()) {
        return nullptr;
    }

    for (auto &session : sessionMap_) {
        SPATIAL_LOGI(MODULE_SERVICE, "sessionMap_.sessionId: %{public}zu", session.second->GetSessionId());
        if (sessionId == session.second->GetSessionId()) {
            return session.second;
        }
    }
    return nullptr;
}

std::shared_ptr<SpatialCommunicator::Session> SpatialCommunicator::GetCommunicatorSession(const std::string &deviceId)
{
    auto sessionIter = sessionMap_.find(deviceId);
    if (sessionIter == sessionMap_.end()) {
        return nullptr;
    }
    return sessionIter->second;
}

int32_t SpatialCommunicator::Connect(const DeviceInfo& device, ConnectionCallback callback)
{
    SPATIAL_LOGI(MODULE_SERVICE, "SpatialCommunicator %{public}s enter", __func__);
    state_ = STATE_NOT_CONNECTED;

    if (state_ >= STATE_CONNECTING) {
        return RET_BUSY;
    }

    state_ = STATE_CONNECTING;
    connectDevice_ = device;
    connectionCb_ = callback;
    return adapter_->Connect(device);
}

bool SpatialCommunicator::IsConnected()
{
    SPATIAL_LOGI(MODULE_SERVICE, "state = %{public}d", state_);
    return (state_ >= STATE_CONNECTED);
}

int32_t SpatialCommunicator::Disconnect(const DeviceInfo& device)
{
    if (state_ < STATE_CONNECTING) {
        return RET_SUCCESS;
    }
    return adapter_->Disconnect();
}

void SpatialCommunicator::RegisterSessionListener(const std::shared_ptr<SpatialSessionListener> &sessionListener)
{
    sessionListener_ = sessionListener;
}

int32_t SpatialCommunicator::CreateSessionServer()
{
    SPATIAL_LOGI(MODULE_SERVICE, "Communicator CreateSessionServer");
    return adapter_->CreateSessionServer();
}

int32_t SpatialCommunicator::RemoveSessionServer()
{
    SPATIAL_LOGI(MODULE_SERVICE, "Communicator RemoveSessionServer");
    int ret = adapter_->RemoveSessionServer();
    if (ret == RET_SUCCESS) {
        state_ = STATE_IDLE;
    }
    return ret;
}

int32_t SpatialCommunicator::OpenSession(const DeviceInfo& device)
{
    if (state_ < STATE_CONNECTED) {
        return RET_NOT_CONNECTED;
    } else if (state_ == STATE_SESSION_OPENING) {
        return RET_BUSY;
    }
    int sessionId = -1;
    SPATIAL_LOGI(MODULE_SERVICE,
        "%{public}s OpenSession, peerDeviceId: %{public}s", __func__, device.deviceId.c_str());

    auto sessionIter = sessionMap_.find(sessionDevice_.deviceId);
    if (sessionIter != sessionMap_.end()) {
        sessionListener_->OnSessionOpen(sessionIter->second);
        return RET_SUCCESS;
    }

    int ret = adapter_->OpenSession(device.deviceId, sessionId);
    if (ret == RET_SUCCESS) {
        SPATIAL_LOGI(MODULE_SERVICE,
            "%{public}s OpenSession sessionId: %{public}d", __func__, sessionId);
        state_ = STATE_SESSION_OPENING;
        sessionDevice_ = device;
        std::shared_ptr<Session> session = std::make_shared<Session>(sessionId, adapter_, sessionDevice_);
        sessionMap_.insert(std::make_pair(sessionDevice_.deviceId, session));
    }

    return ret;
}

int32_t SpatialCommunicator::CloseSession(int sessionId)
{
    SPATIAL_LOGI(MODULE_SERVICE, " CloseSession state: %{public}d", state_);
    if (state_ < STATE_CONNECTED) {
        return RET_NOT_CONNECTED;
    } else if (state_ == STATE_SESSION_OPENING) {
        return RET_BUSY;
    }
    auto session = GetCommunicatorSession(sessionId);
    if (session == nullptr) {
        SPATIAL_LOGE(MODULE_SERVICE, " CloseSession state: %{public}d", state_);
        return SPATIALMANAGER_FAILED;
    }

    adapter_->CloseSession(sessionId);
    sessionOpenFlag_  = false;
    state_ = STATE_IDLE;
    return SPATIALMANAGER_OK;
}

bool SpatialCommunicator::IsServer(int sessionId)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s enter", __func__);
    return adapter_->IsServer(sessionId);
}

void SpatialCommunicator::OnPublishSuccess()
{
    SPATIAL_LOGI(MODULE_SERVICE, "OnPublishSuccess");
}

void SpatialCommunicator::OnGetSuccess(const std::vector<DeviceInfo> &deviceInfoList)
{
    SPATIAL_LOGI(MODULE_SERVICE, "OnGetSuccess");
    if (getting_) {
        trustDeviceCb_(GET_SUCCESS, deviceInfoList);
        getting_ = false;
    }
}

void SpatialCommunicator::OnGetFail(const std::vector<DeviceInfo> &deviceInfoList)
{
    if (getting_) {
        trustDeviceCb_(GET_FAILED, deviceInfoList);
    }
}

void SpatialCommunicator::OnDiscoverStart()
{
    SPATIAL_LOGI(MODULE_SERVICE, "OnDiscoverStart");
    if (discovering_) {
        DeviceInfo device;
        discoverCb_(DISCOVER_START, device);
    }
}
          
void SpatialCommunicator::OnDiscoverDevice(const DeviceInfo& device)
{
    SPATIAL_LOGI(MODULE_SERVICE, "OnDiscoverDevice %{public}s", device.deviceId.c_str());
    if (discovering_) {
        discoverCb_(DISCOVER_DEVICE, device);
    }
}

void SpatialCommunicator::OnDiscoverEnd()
{
    SPATIAL_LOGI(MODULE_SERVICE, "OnDiscoverEnd");
    if (discovering_) {
        DeviceInfo device;
        discovering_ = false;
        discoverCb_(DISCOVER_END, device);
    }
}

void SpatialCommunicator::OnConnected(const std::string& networkId)
{
    SPATIAL_LOGI(MODULE_SERVICE, "SpatialCommunicator OnConnect");
    if (state_ == STATE_CONNECTING) {
        SPATIAL_LOGI(MODULE_SERVICE, "state = %{public}d", state_);
        state_ = STATE_CONNECTED;
        connectDevice_.networkId = networkId;
        connectionCb_(CONNECTED, connectDevice_);
    }
}

void SpatialCommunicator::OnDisconnected()
{
    SPATIAL_LOGI(MODULE_SERVICE, "OnDisconnect state=%{public}d", state_);
    if (state_ >= STATE_CONNECTING) {
        state_ = STATE_IDLE;
        if (connectionCb_ == nullptr) {
            SPATIAL_LOGE(MODULE_SERVICE, "connectionCb_ is nullptr");
            return;
        }
        connectionCb_(DISCONNECTED, connectDevice_);
    }
}

int32_t SpatialCommunicator::OnSessionOpened(int sessionId, int result)
{
    SPATIAL_LOGI(MODULE_SERVICE, "sessionId: %{public}d, result: %{public}d",
        sessionId, result);
    if (result == SPATIALMANAGER_OK) {
        SPATIAL_LOGI(MODULE_SERVICE, "SpatialCommunicator: success to open session");
        std::lock_guard<std::mutex> lock(mutex_);
        if (sessionListener_ == nullptr) {
            SPATIAL_LOGW(MODULE_SERVICE, "OnSessionOpened can't find listener");
            return SPATIALMANAGER_FAILED;
        }
        sessionOpenFlag_ = true;
        state_ = STATE_SESSION_OPENED;
        std::string peerDeviceId;
        adapter_->GetPeerDeviceId(sessionId, peerDeviceId);
        SPATIAL_LOGI(MODULE_SERVICE, "peerdeviceId: %{public}s", peerDeviceId.c_str());
        auto deviceIdIter = sessionMap_.find(peerDeviceId);
        if (deviceIdIter != sessionMap_.end()) {
            deviceIdIter->second->SetSessionId(sessionId);
        } else {
            std::shared_ptr<Session> session = std::make_shared<Session>(sessionId, adapter_, sessionDevice_);
            sessionMap_.insert(std::make_pair(peerDeviceId, session));
            sessionListener_->OnSessionOpen(session);
        }
    } else {
        SPATIAL_LOGI(MODULE_SERVICE, "session open failed, sessionId %{public}d", sessionId);
        auto session = GetCommunicatorSession(sessionId);
        if (session == nullptr) {
            return SPATIALMANAGER_FAILED;
        }
        int sessionId = -1;
        adapter_->OpenSession(session->GetDeviceInfo().deviceId, sessionId);
        session->SetSessionId(sessionId);
    }
    return SPATIALMANAGER_OK;
}

void SpatialCommunicator::OnSessionClosed(int sessionId)
{
    SPATIAL_LOGI(MODULE_SERVICE, "OnSessionClosed %{public}d", sessionId);
    if (sessionOpenFlag_) {
        SPATIAL_LOGE(MODULE_SERVICE, "not spatial awareness causes sessionclose");
        return;
    }

    if (sessionMap_.empty()) {
        return;
    }

    for (auto sessionIter = sessionMap_.begin(); sessionIter != sessionMap_.end(); sessionIter++) {
        if (sessionIter->second->GetSessionId() == sessionId) {
            sessionListener_->OnSessionClose(sessionIter->second);
            sessionMap_.erase(sessionIter);
            break;
        }
    }
    state_ = STATE_IDLE;
}

void SpatialCommunicator::OnBytesReceived(int sessionId, const void *data, unsigned int dataLen)
{
    SPATIAL_LOGI(MODULE_SERVICE, "OnBytesReceived %{public}d", sessionId);
}

void SpatialCommunicator::OnMessageReceived(int sessionId, const void *data, unsigned int dataLen)
{
    std::lock_guard<std::mutex> lock(mutex_);
    SPATIAL_LOGI(MODULE_SERVICE,
        "OnMessageReceived, sessionId:%{public}d, dataLen:%{public}d, size=%{public}d",
        sessionId, dataLen, sessionMap_.size());
    auto session = GetCommunicatorSession(sessionId);
    if (session == nullptr) {
        SPATIAL_LOGI(MODULE_SERVICE, "OnMessageReceived, session is nullptr");
        return;
    }
    std::string str(reinterpret_cast<const char*>(data));
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s str=%{public}s", __func__, str.c_str());
    std::shared_ptr<MessageBuilder> builder = std::make_shared<MessageBuilder>(sessionId, str);
    if (builder->IsRequest()) {
        SPATIAL_LOGI(MODULE_SERVICE, "OnReceiveRequest");
        std::shared_ptr<SpatialRequest> request = builder->GetRequest();
        request->sessionId = session->GetSessionId();
        sessionListener_->OnReceiveRequest(session, request);
    } else if (builder->IsResponse()) {
        SPATIAL_LOGI(MODULE_SERVICE, "OnReceiveResponse");
        std::shared_ptr<SpatialResponse> response = builder->GetResponse();
        response->sessionId = session->GetSessionId();
        sessionListener_->OnReceiveResponse(session, response);
    } else {
        SPATIAL_LOGW(MODULE_SERVICE, "OnMessageReceived invalid message");
    }
}

void SpatialCommunicator::OnError(int error)
{
    SPATIAL_LOGI(MODULE_SERVICE, "OnError %{public}d", error);
}

void SpatialCommunicator::OnDeviceOnline(const DeviceInfo& device)
{
    stateCb_(ONLINE, device);
}

void SpatialCommunicator::OnDeviceReady(const DeviceInfo& device)
{
    stateCb_(READY, device);
}

void SpatialCommunicator::OnDeviceOffline(const DeviceInfo& device)
{
    stateCb_(OFFLINE, device);
}

void SpatialCommunicator::OnDeviceChanged(const DeviceInfo& device)
{
    stateCb_(CHANGED, device);
}

SpatialCommunicator::Session::Session(int sessionId, std::shared_ptr<ICommunicateInterface>& adapter,
    const DeviceInfo& device)
{
    sessionId_ = sessionId;
    adapter_ = adapter;
    deviceInfo_ = device;
}

SpatialCommunicator::Session::~Session()
{
}

int SpatialCommunicator::Session::GetSessionId()
{
    return sessionId_;
}

void SpatialCommunicator::Session::SetSessionId(int sessionId)
{
    sessionId_ = sessionId;
}

DeviceInfo SpatialCommunicator::Session::GetDeviceInfo()
{
    return deviceInfo_;
}

int SpatialCommunicator::Session::GetRole()
{
    bool ret = adapter_->IsServer(sessionId_);
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s sessionId_: %{public}d, ret: %{public}d", __func__, sessionId_, ret);
    if (ret) {
        return ROLE_SERVER;
    } else {
        return ROLE_CLIENT;
    }
}

int32_t SpatialCommunicator::Session::RequestGetMap()
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s enter", __func__);
    std::shared_ptr<SpatialRequest> request = std::make_shared<SpatialRequest>();
    request->requestId = SpatialRequestId::REQUEST_GET_MAP;
    request->sessionId = sessionId_;
    std::unique_ptr<MessageBuilder> builder = std::make_unique<MessageBuilder>(request);
    std::string str = builder->GetString();
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s message: %{public}s", __func__, str.c_str());
    return adapter_->SendMessage(sessionId_,
        reinterpret_cast<const void*>(str.c_str()), str.length());
}

int32_t SpatialCommunicator::Session::RequestSyncMapOffer(uint32_t topCommit)
{
    std::shared_ptr<SpatialRequest> request = std::make_shared<SpatialRequest>();
    request->requestId = SpatialRequestId::REQUEST_SYNC_MAP_OFFER;
    request->sessionId = sessionId_;
    request->value = topCommit;
    std::unique_ptr<MessageBuilder> builder = std::make_unique<MessageBuilder>(request);
    std::string str = builder->GetString();
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s message: %{public}s", __func__, str.c_str());
    return adapter_->SendMessage(sessionId_,
        reinterpret_cast<const void*>(str.c_str()), str.length());
}

int32_t SpatialCommunicator::Session::RequestSyncMap(std::shared_ptr<SpatialMap> map)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s enter", __func__);
    std::shared_ptr<SpatialRequest> request = std::make_shared<SpatialRequest>();
    request->requestId = SpatialRequestId::REQUEST_SYNC_MAP;
    request->sessionId = sessionId_;
    request->map = map;
    std::unique_ptr<MessageBuilder> builder = std::make_unique<MessageBuilder>(request);
    std::string str = builder->GetString();
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s message: %{public}s", __func__, str.c_str());
    return adapter_->SendMessage(sessionId_,
        reinterpret_cast<const void*>(str.c_str()), str.length());
}

int32_t SpatialCommunicator::Session::ResponseGetMap(std::shared_ptr<SpatialMap> map)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s enter", __func__);
    std::shared_ptr<SpatialResponse> response = std::make_shared<SpatialResponse>();
    response->responseId = SpatialResponseId::RESPONSE_GET_MAP;
    response->sessionId = sessionId_;
    response->map = map;
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s map.size: %{public}d", __func__, map->map.size());
    if (map->map.size() <= 1) {
        response->status = CommunicationStatus::NO_CONTENT;
    } else {
        response->status = CommunicationStatus::SUCCESS;
        response->map = map;
    }
    std::unique_ptr<MessageBuilder> builder = std::make_unique<MessageBuilder>(response);
    std::string str = builder->GetString();
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s message: %{public}s", __func__, str.c_str());
    return adapter_->SendMessage(sessionId_,
        reinterpret_cast<const void*>(str.c_str()), str.length());
}

int32_t SpatialCommunicator::Session::ResponseSyncMapOffer(bool accept)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s enter", __func__);
    std::shared_ptr<SpatialResponse> response = std::make_shared<SpatialResponse>();
    response->responseId = SpatialResponseId::RESPONSE_SYNC_MAP_OFFER;
    response->sessionId = sessionId_;
    if (accept) {
        response->status = CommunicationStatus::ACCEPT;
    } else {
        response->status = CommunicationStatus::REJECT;
    }
    std::unique_ptr<MessageBuilder> builder = std::make_unique<MessageBuilder>(response);
    std::string str = builder->GetString();
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s message: %{public}s", __func__, str.c_str());
    return adapter_->SendMessage(sessionId_,
        reinterpret_cast<const void*>(str.c_str()), str.length());
}

int32_t SpatialCommunicator::Session::ResponseSyncMap(bool success)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s enter", __func__);
    std::shared_ptr<SpatialResponse> response  = std::make_shared<SpatialResponse>();
    response->responseId = SpatialResponseId::RESPONSE_SYNC_MAP_OFFER;
    response->sessionId = sessionId_;
    if (success) {
        response->status = CommunicationStatus::SUCCESS;
    } else {
        response->status = CommunicationStatus::FAIL;
    }
    std::unique_ptr<MessageBuilder> builder = std::make_unique<MessageBuilder>(response);
    std::string str = builder->GetString();
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s str=%{public}s", __func__, str.c_str());
    return adapter_->SendMessage(sessionId_,
        reinterpret_cast<const void*>(str.c_str()), str.length());
}
} // namespace Msdp
} // namespace OHOS